<?php
/**
 * Created by PhpStorm.
 * User: whwyy
 * Date: 2018/11/8 0008
 * Time: 18:15
 */

namespace BeReborn\Service\Common;

use BeReborn;
use BeReborn\Annotation\Annotation;
use BeReborn\Http\HttpHeaders;
use BeReborn\Http\HttpParams;
use Exception;
use ReflectionException;
use Swoole\Http\Request as SRequest;
use Swoole\Http\Response as SResponse;
use Swoole\WebSocket\Frame;
use Swoole\WebSocket\Server;
use BeReborn\Service\Base\ServerBase;
use BeReborn\Event\Event;
use BeReborn\Http\Request;
use BeReborn\Http\Context;
use BeReborn\Error\Logger;

/**
 * Class ServerWebSocket
 * @package BeReborn\Server
 */
class ServerWebSocket extends ServerBase
{
    const EVENT_HANDSHAKE = 'handshake';
    const EVENT_CLOSE = 'close';

    public $namespace = 'App\\Sockets\\';

    public $callback = [];

    /**
     * @param Server $server
     * @param Frame $frame
     * @throws
     */
    public function onMessage(Server $server, Frame $frame)
    {
        try {
            if ($frame->opcode == 0x08) {
                return;
            }

            $json = json_decode($frame->data, true);
            if (is_null($json) || !isset($json['route'])) {
                return;
            }

            ServerWebSocket::setRequestDi($json, $frame->fd);
            if (isset($json['body']) && !empty($json['body'])) {
                Input()->setPosts($json['body']);
            }

            /** @var Annotation $manager */
            $manager = \BeReborn::getApp('annotation');
            $manager->runWith($this->getName($json), [$frame->fd, $server]);
        } catch (Exception $exception) {
            $this->error($exception->getMessage(), __METHOD__, __FILE__);
            $this->addError($exception->getMessage());
        } finally {
            fire(ServerRequest::AFTER_REQUEST);
            Logger::insert();
        }
    }

    /**
     * @param $json
     * @return string
     */
    private function getName($json)
    {
        return 'WEBSOCKET:MESSAGE:' . $json['route'];
    }

    /**
     * @param array $data
     * @param $fd
     * @throws Exception
     */
    public static function setRequestDi($data, $fd)
    {
        $req = new Request();
        $req->startTime = microtime(true);
        $req->params = new HttpParams($data, [], []);
        $req->headers = new HttpHeaders([
            'request_method' => 'socket',
            'request_uri' => $data['route']
        ]);
        $req->setFd($fd);

        Context::setRequest($req);
    }

    /**
     * @param SRequest $request
     * @param SResponse $response
     * @return bool
     * @throws Exception
     */
    protected function connect($request, $response)
    {
        /** @var Annotation $manager */
        $manager = \BeReborn::getApp('annotation');

        $event = 'WEBSOCKET:EVENT:' . self::EVENT_HANDSHAKE;
        if ($manager->has($event)) {
            $result = $manager->runWith($event, [$request, $response]);
        } else {
            $response->status(502);
            $response->end();
            $result = true;
        }
        fire(ServerRequest::AFTER_REQUEST);
        Logger::insert();
        return $result;
    }

    /**
     * @param SRequest $request
     * @param SResponse $response
     * @return bool|string
     * @throws Exception
     */
    public function onHandshake(SRequest $request, SResponse $response)
    {
        /** @var Server $server */
        $secWebSocketKey = $request->header['sec-websocket-key'];
        $patten = '#^[+/0-9A-Za-z]{21}[AQgw]==$#';
        if (0 === preg_match($patten, $secWebSocketKey) || 16 !== strlen(base64_decode($secWebSocketKey))) {
            return false;
        }
        $key = base64_encode(sha1(
            $request->header['sec-websocket-key'] . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11',
            TRUE
        ));
        $headers = [
            'Upgrade' => 'websocket',
            'Connection' => 'Upgrade',
            'Sec-websocket-Accept' => $key,
            'Sec-websocket-Version' => '13',
        ];
        if (isset($request->header['sec-websocket-protocol'])) {
            $headers['Sec-websocket-Protocol'] = $request->header['sec-websocket-protocol'];
        }
        foreach ($headers as $key => $val) {
            $response->header($key, $val);
        }

        if (isset($request->get['debug']) && $request->get['debug'] == 'test') {
            BeReborn::$app->redis->sAdd('debug:list', $request->fd);
            $response->status(101);
            $response->end();
            return true;
        } else {
            return $this->connect($request, $response);
        }
    }

    /**
     * @param Server $server
     * @param int $fd
     * @throws Exception
     */
    public function onClose(Server $server, int $fd)
    {
        try {
            if (!$server->isEstablished($fd)) {
                return;
            }
            $redis = BeReborn::$app->redis;
            if ($redis->sismember('debug:list', $fd)) {
                $redis->sRem('debug:list', $fd);
            }

            /** @var Annotation $manager */
            $event = 'WEBSOCKET:EVENT:' . self::EVENT_CLOSE;
            $manager = \BeReborn::getApp('annotation');
            if ($manager->has($event)) {
                $manager->runWith($event, [$fd]);
            }
        } catch (\Throwable $exception) {
            $this->addError($exception->getMessage());
        } finally {
            fire(ServerRequest::AFTER_REQUEST);
            Logger::insert();
        }
    }

}
